/*
	NounSpawnShip.cpp
	(c)2000 Palestar Inc, Richard Lyle
*/


#include "Math/Helpers.h"
#include "NounSpawnShip.h"
#include "VerbRevolt.h"
#include "GameContext.h"

//---------------------------------------------------------------------------------------------------

struct RomanDigit
{
	char *				m_psString;
	unsigned int		m_nValue;
};

static const RomanDigit ROMAN_DIGITS[]=
{
	{"M",  1000},
	{"CM",  900},
	{"D",   500},
	{"CD",  400},
	{"C",   100},
	{"XC",   90},
	{"L",    50},
	{"XL",   40},
	{"X",    10},
	{"IX",    9},
	{"V",     5},
	{"IV",    4},
	{"I",     1},
};

static CharString GenerateRomanNumerals( unsigned int a_nNumber )
{
	CharString digits;
	for(int i=0; a_nNumber > 0 && i < sizeof(ROMAN_DIGITS)/sizeof(ROMAN_DIGITS[0]);++i)
	{
		while( ROMAN_DIGITS[i].m_nValue <= a_nNumber )
		{
			digits += ROMAN_DIGITS[i].m_psString;
			a_nNumber -= ROMAN_DIGITS[i].m_nValue;
		}
	}

	return digits;
}

//---------------------------------------------------------------------------------------------------

const int MIN_SPAWN_DELAY = TICKS_PER_SECOND * 5;
const int MAX_ATTEMPTS = 10;
const int MAX_PLACEMENT_ATTEMPTS = 10;

//---------------------------------------------------------------------------------------------------

IMPLEMENT_NAMED_WIDGET_TYPES( TYPES_Spawner, NounSpawnShip::Spawner );
IMPLEMENT_NAMED_FACTORY( FACTORY_Spawner, NounSpawnShip::Spawner, Widget );
BEGIN_NAMED_PROPERTY_LIST( PLIST_Spawner, NounSpawnShip::Spawner, Widget );
	ADD_PROPERTY( m_pNounTemplate );
	ADD_PROPERTY( m_sBaseName );
	ADD_PROPERTY( m_bNumbered );
	ADD_PROPERTY( m_nLimit );
	ADD_PROPERTY( m_nChance );
	ADD_PROPERTY( m_nScore );
	ADD_PROPERTY( m_Order );
	ADD_PROPERTY( m_sOrderTarget );
	ADD_PROPERTY( m_Spawned );
END_PROPERTY_LIST();

void NounSpawnShip::Spawner::update()
{
	// remove any destroyed ships..
	for(int i=m_Spawned.size()-1;i>=0;--i)
	{
		if ( !m_Spawned[i].valid() || m_Spawned[i]->destroyed() || m_Spawned[i]->parent() == NULL )
			m_Spawned.removeSwap( i );
	}
}

NounShip * NounSpawnShip::Spawner::spawn()
{
	if (! m_pNounTemplate )
		return NULL;

	// use a reference pointer, so the object will be deleted if not a NounShip object..
	Noun::Ref pUncasted = m_pNounTemplate->spawn();

	NounShip * pShip = WidgetCast<NounShip>( pUncasted );
	if (! pShip )
		return NULL;

	if ( m_bNumbered )
		pShip->setName( m_sBaseName + " " + GenerateRomanNumerals( m_nNextNumber++ ) );
	else
		pShip->setName( m_sBaseName );

	m_Spawned.push( pShip );
	return pShip;
}

//---------------------------------------------------------------------------------------------------

IMPLEMENT_FACTORY( NounSpawnShip, NounBody );
REGISTER_FACTORY_KEY( NounSpawnShip, 4153031812113093571 );

BEGIN_PROPERTY_LIST( NounSpawnShip, NounBody )
	ADD_PROPERTY( m_nSpawnType );
	ADD_PROPERTY( m_nSpawnDelay );
	ADD_PROPERTY( m_fSpawnArea );
	ADD_PROPERTY( m_nMinSpawn );
	ADD_PROPERTY( m_nMaxSpawn );
	ADD_PROPERTY( m_nMinPlanets );
	ADD_PROPERTY( m_Spawners );
END_PROPERTY_LIST();

NounSpawnShip::NounSpawnShip() :
	m_nSpawnType( ST_NORMAL ),
	m_nSpawnTick( 0 ),
	m_nSpawnDelay( TICKS_PER_SECOND * 30 ),
	m_fSpawnArea( 0.0f ),
	m_nMinSpawn( 0 ),
	m_nMaxSpawn( 1 ),
	m_nMinPlanets( 0 )
{}

//----------------------------------------------------------------------------

NounType NounSpawnShip::nounType() const
{
	return TYPE_SERVER_OBJECT;
}

bool NounSpawnShip::canBeDetected() const
{
	return false;		// object cannot be detected
}

void NounSpawnShip::initialize()
{
	NounBody::initialize();

	m_nSpawnTick = 0;
	if ( m_nSpawnDelay < MIN_SPAWN_DELAY )
		m_nSpawnDelay = MIN_SPAWN_DELAY;

	// validate that we have valid spawner objects..
	for(int i=0;i<m_Spawners.size();)
	{
		if (! m_Spawners[i].valid() )
			m_Spawners.removeSwap( i );
		else
			++i;
	}
}

void NounSpawnShip::release()
{
	NounBody::release();
}

void NounSpawnShip::simulate( dword nTick )
{
	if ( server() && nTick >= m_nSpawnTick && m_Spawners.size() > 0 )
	{
		// reset the count
		m_nSpawnTick = nTick + m_nSpawnDelay;

		if ( m_nMinPlanets > 0 )
		{
			int				nFactionPlanets = 0;
			int				nFactionId = factionId();
			NounPlanet *	pBestPlanet = NULL;
			float			fBestDistance = 0.0f;

			for(int j=0;j<context()->zoneCount() && nFactionPlanets < m_nMinPlanets;j++)
			{
				NodeZone * pZone = context()->zone( j );
				for(int i=0;i<pZone->childCount() && nFactionPlanets < m_nMinPlanets;i++)
				{
					NounPlanet * pPlanet = WidgetCast<NounPlanet>( pZone->child(i) );
					if (! pPlanet )
						continue;
					if ( pPlanet->maxUnits() <= 0 )
						continue;

					if ( pPlanet->factionId() != nFactionId )
					{
						float fDistance = (pPlanet->worldPosition() - worldPosition()).magnitude();
						if ( !pBestPlanet || fDistance < fBestDistance )
						{
							pBestPlanet = pPlanet;
							fBestDistance = fDistance;
						}
						continue;
					}

					++nFactionPlanets;
				}
			}

			if( nFactionPlanets < m_nMinPlanets )
				Verb::Ref( new VerbRevolt( pBestPlanet, teamId() ) );
		}

		// update all current spawner objects and tally the total chance & spawn score ...
		int nTotalChance = 0, nTotalSpawnScore = 0;
		for(int i=0;i<m_Spawners.size();++i)
		{
			Spawner * pSpawner = m_Spawners[ i ];
			// update the spawner first, so it will remove any dead ships that might affect it's score..
			pSpawner->update();

			// tally up some values..
			nTotalSpawnScore += pSpawner->spawnedScore();
			if ( pSpawner->limit() > 0 && pSpawner->spawnedCount() >= pSpawner->limit() )
				continue;		// at limit, skip this spawner..
			nTotalChance += pSpawner->chance();
		}

		int nMaxSpawn = m_nMaxSpawn;
		switch( m_nSpawnType )
		{
		case ST_NORMAL:
		case ST_ONE_SHOT:
			break;
		case ST_POPULATION:
			{
				// max spawn increases as player populations increase.. 
				int nPlayers = context()->user()->playerCount();
				int nMaxPlayers = context()->user()->maxPlayers();
				float fSpawnScale = 0.0f;
				if ( nMaxPlayers > 0 )
					fSpawnScale = ((float)nPlayers) / ((float)nMaxPlayers);
				nMaxSpawn = (int)ceilf( fSpawnScale * nMaxSpawn );
			}
			break;
		case ST_FACTION:
			{
				// max spawn increases as player populations decrease..
				int nMaxPlayers = context()->user()->maxPlayers();
				int nFactionPlayers = context()->user()->playerFactionCount( factionId() );

				// what percentage of the players are in our faction...
				float fPlayersInFaction = 1.0f;
				if ( nMaxPlayers > 0 )
					fPlayersInFaction = 1.0f - (((float)nFactionPlayers) / ((float)nMaxPlayers));

				nMaxSpawn = (int)ceilf( fPlayersInFaction * nMaxSpawn );
			}
			break;
		}

		if ( nMaxSpawn < m_nMinSpawn )
			nMaxSpawn = m_nMinSpawn;

		// spawn ships... 
		int nAttempts = 0;
		while( nTotalSpawnScore < nMaxSpawn )
		{
			// increment the number of attempts..
			if ( ++nAttempts > MAX_ATTEMPTS )
				break;

			// select a random spawner
			int nPick = rand() % nTotalChance;

			Spawner * pSpawner = NULL;
			for(int i=0;i<m_Spawners.size() && nPick >= 0;++i)
			{
				pSpawner = m_Spawners[ i ];
				if ( pSpawner->limit() > 0 && pSpawner->spawnedCount() >= pSpawner->limit() )
					continue;		// at limit, skip this spawner..
				nPick -= pSpawner->chance();
			}

			if (! pSpawner )
				continue;
			NounShip * pSpawn = pSpawner->spawn();
			if (! pSpawn )
				continue;

			int nPlaceAttempts = 0;
			while( nPlaceAttempts < MAX_PLACEMENT_ATTEMPTS )
			{
				Vector3 vOffset( RandomFloat(-m_fSpawnArea,m_fSpawnArea), 0.0f, RandomFloat(-m_fSpawnArea,m_fSpawnArea) );
				Vector3 vPosition( worldPosition() + vOffset );

				Array< GameContext::NounCollision > collisions;
				if (! context()->proximityCheck( vPosition, pSpawn->radius() * 1.5f, collisions ) )
				{
					pSpawn->setPosition( vPosition );
					break;
				}

				++nPlaceAttempts;
			}

			if ( nPlaceAttempts >= MAX_PLACEMENT_ATTEMPTS )
				break;

			Matrix33 vFrame( worldFrame() );
			pSpawn->setFrame( vFrame );
			pSpawn->setTeamId( teamId() );
			pSpawn->setHeading( atan2( vFrame.k.x, vFrame.k.z ) );
			pSpawn->setup();
			
			context()->attachNoun( pSpawn );

			// set the order after the object is attached on purpose
			if ( pSpawner->order() != NounShip::NOORDER )
			{
				Noun * pOrderTarget = context()->findNoun( pSpawner->orderTarget() );
				pSpawn->setOrder( pSpawner->order(), pOrderTarget, pSpawn );
			}

			nTotalSpawnScore += pSpawner->score();
			if ( pSpawner->limit() > 0 && pSpawner->spawnedCount() >= pSpawner->limit() )
				nTotalChance -= pSpawner->chance();
		}

		// detach this node from the world if this will never recur again
		if ( m_nSpawnType == ST_ONE_SHOT )
			setDetach();
	}

	NounBody::simulate( nTick );
}

//---------------------------------------------------------------------------------------------------

void NounSpawnShip::addSpawner( Spawner * a_pSpawn )
{
	m_Spawners.push( a_pSpawn );
}

void NounSpawnShip::removeSpawner( int n )
{
	m_Spawners.removeSwap( n );
}

void NounSpawnShip::releaseSpawners()
{
	m_Spawners.release();
}

//----------------------------------------------------------------------------
//EOF
